package com.xb.chain.pow;

import com.xb.chain.entity.Block;
import com.xb.chain.utils.ByteUtils;
import com.xb.chain.utils.SerializeUtils;
import lombok.Data;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;

import java.math.BigInteger;

/**
 * @author xiaobin
 * @date 2020/2/20 10:55
 * @desc
 */
@Data
public class ProofOfWork {


    private static final Integer diff = 16;

    private Block block;

    private BigInteger targetValue;

    public ProofOfWork(Block block, BigInteger targetValue) {
        this.block = block;
        this.targetValue = targetValue;
    }

    /**
     * 新建对象工作量评估
     *
     * @param block
     * @return
     */
    public static ProofOfWork newPow(Block block) {
        BigInteger targetValue = BigInteger.valueOf(1).shiftLeft((256 - diff));
        return new ProofOfWork(block, targetValue);
    }

    /**
     * 将对象转换为byte字节
     *
     * @param nonce
     * @return
     */
    private byte[] prepareData(long nonce) {
        byte[] prevBlockHashBytes = {};
        if (StringUtils.isNoneBlank(this.getBlock().getPreviousHash())) {
            prevBlockHashBytes = new BigInteger(this.getBlock().getPreviousHash(), 16).toByteArray();
        }

        return ByteUtils.merge(
                prevBlockHashBytes,
                SerializeUtils.serialize(this.getBlock().getTransactions()),
                ByteUtils.toBytes(this.getBlock().getTime()),
                ByteUtils.toBytes(diff),
                ByteUtils.toBytes(nonce)
        );
    }

    /**
     * 计算Hash值
     *
     * @return
     */
    public PowResult compute() {
        int nonce = 0;
        String data = "";
        do {
            nonce++;
            byte[] shaHex = prepareData(nonce);
            data = DigestUtils.sha256Hex(shaHex);
        } while (new BigInteger(data, 16).compareTo(this.targetValue) != -1);
        return new PowResult(data, nonce);
    }

    /**
     * 验证区块是否有效
     *
     * @return
     */
    public boolean validate() {
        byte[] data = this.prepareData(this.getBlock().getNonce());
        return new BigInteger(DigestUtils.sha256Hex(data), 16).compareTo(this.targetValue) == -1;
    }

}


